package com.product.statemachine.common;

import com.product.statemachine.common.entity.IEntity;
import com.product.statemachine.common.event.StateEvent;
import com.product.statemachine.common.exception.StateMachineException;
import com.product.statemachine.common.executor.DomainStateEventExecutor;
import com.product.statemachine.common.state.State;
import com.product.statemachine.common.util.ClassUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;

import java.util.*;

/**
  * @author: caoxingming
  * @data: 2022/6/9 12:03 下午
  * @description: 事件状态机实现类
  **/
@Slf4j
public class DomainStateMachine{

    /**
     * 状态机名
     */
    private final String domainStateMachineName;

    /**
     * 状态机状态map
     *      key:StateName
     *      value:State 对象
     */
    private HashMap<String, State> stateMap = new HashMap<>(50);

    /**
     * 状态机事件map
     *      key:stateEventName
     *      value:StateEvent 对象
     */
    private HashMap<String, StateEvent> stateEventMap = new HashMap<>(50);

    /**
     * 状态机事件执行器map
     *      key:stateEvent Class全路径
     *      value:DomainStateEventExecutor 对象
     */
    private HashMap<String, DomainStateEventExecutor> domainStateEventExecutorMap = new HashMap<>(50);

    /**
     * 状态机事件执行器执行
     * @param entity
     * @param stateEventClass
     */
    public void transition(IEntity entity, Class<? extends StateEvent> stateEventClass) {
        StateEvent stateEvent = this.getStateEventInstance(stateEventClass);
        Optional.ofNullable(stateEvent).<StateMachineException>orElseThrow(()->{ throw new StateMachineException("found no state event class"); });
        DomainStateEventExecutor stateEventExecutor = this.getStateEventExecutorInstance(stateEventClass);
        Optional.ofNullable(stateEventExecutor).<StateMachineException>orElseThrow(()->{ throw new StateMachineException("found no state event executor"); });
        System.out.println("before状态机" + this.domainStateMachineName + "事件" + stateEvent.toString() + "前置处理.....");
        stateEventExecutor.before(entity,stateEvent);
        System.out.println("after状态机" + this.domainStateMachineName + "事件" + stateEvent.toString() + "前置处理.....");

        System.out.println("before状态机" + this.domainStateMachineName + "事件" + stateEvent.toString() + "标准处理.....");
        //检查状态机事件原状态是否和当前领域实体状态一致
        entity.checkSourceStateIsEqualsToEntityState(stateEvent);
        stateEventExecutor.execute(entity,stateEvent);
        System.out.println("after状态机" + this.domainStateMachineName + "事件" + stateEvent.toString() + "标准处理.....");

        System.out.println("before状态机" + this.domainStateMachineName + "事件" + stateEvent.toString() + "后置处理.....");
        stateEventExecutor.after(entity,stateEvent);
        System.out.println("after状态机" + this.domainStateMachineName + "事件" + stateEvent.toString() + "后置处理.....");

    }

    /**
     * 获取状态机事件执行器实例
     * @param clazz
     * @return
     */
    public DomainStateEventExecutor getStateEventExecutorInstance(Class<? extends StateEvent> clazz) {
        if(CollectionUtils.isEmpty(domainStateEventExecutorMap)) {
            return null;
        }
        return domainStateEventExecutorMap.get(ClassUtils.getClassPackageName(clazz));
    }


    /***
     * 获取状态机事件对象实例
     * @param clazz
     * @return
     */
    public StateEvent getStateEventInstance(Class<? extends StateEvent> clazz) {
        if(CollectionUtils.isEmpty(stateEventMap)) {
            return null;
        }
        List<StateEvent> stateEvents =  new ArrayList<>();
        stateEventMap.entrySet().stream().forEach(stateEventEntry -> {
            StateEvent stateEvent = stateEventEntry.getValue();
            stateEvents.add(stateEvent);
        });

        for(StateEvent stateEvent : stateEvents) {
            if(clazz.equals(stateEvent.getClass())) {
                return stateEvent;
            }
        }
        return null;
    }


    /***
     * 构造方法
     * @param domainStateMachineName
     * @param stateEventMap
     * @param stateMap
     * @param domainStateEventExecutorMap
     */
    public DomainStateMachine(String domainStateMachineName, HashMap<String, State> stateMap, HashMap<String, StateEvent> stateEventMap, HashMap<String, DomainStateEventExecutor> domainStateEventExecutorMap) {
        this.domainStateMachineName = domainStateMachineName;
        this.stateMap = stateMap;
        this.stateEventMap = stateEventMap;
        this.domainStateEventExecutorMap = domainStateEventExecutorMap;
    }

    public String getDomainStateMachineName() {
        return domainStateMachineName;
    }

    public HashMap<String, State> getStateMap() {
        return stateMap;
    }

    public HashMap<String, StateEvent> getStateEventMap() {
        return stateEventMap;
    }

    public HashMap<String, DomainStateEventExecutor> getDomainStateEventExecutorMap() {
        return domainStateEventExecutorMap;
    }


}
